home *** CD-ROM | disk | FTP | other *** search
- Using Replay Sound Code
- =======================
-
- When sound compression field is 1, there are 10 different sound formats
- currently available for Replay:
-
- 4bit: mono and stereo 4 bit ADPCM SoundA4, SoundA4x2
-
- 8bit: mono and stereo 8 bit linear signed SoundS8, SoundS8x2
- linear unsigned SoundU8, SoundU8x2
- exponential (uLaw) SoundE8, SoundE8x2
-
- 16bit: mono and stereo 16 bit linear signed SoundS16, SoundS16x2
-
- When sound compression field is 2, the actual name of the sound decompressor
- follows the field; for example '2 ADPCM' would provide a new format which used
- the decompressor components in ADPCM: this is a directory in
- <ARMovie$SoundDir>.
-
- For all formats, there is an appropriate piece of machine code which can be
- called to play the format directly. For format 2 there is a set of resources
- which can be used to play, compress and decompress the sound data, though only
- some of these may be present. These are all stored in the named directory
- inside <ARMovie$SoundDir>. A full implementation of a format 2 system would
- look like:
-
- ADPCM.Play play the data out of the computer
- Playx2 ditto, but stereo
- Playx??? extra files possible for more channels
- To16 convert data to 16 bit linear signed
- From16 convert data from 16 bit linear signed
- Info find out information on the format
-
- Only the Play (or Playx2) and Info files are essential. The Info file
- contains the following:
-
- line 1: name of compression
- 2: copyright details
- 3: if 0, decompression can only start at beginning of chunk;
- if 1, decompression can start at any sample in chunk;
- other values reserved.
- 4: n: buffer size multiplier - how many bits of storage per sample the
- Play program needs to work: see section (3).
- 5: if 0, compression is in fixed ratio (i.e. constant bits/sample); if
- 1, compression ratio is variable (but bounded). See section (10).
- 6: c: maximum size of a compressed sample, in bits. This is a real (not
- just integer) number, since some schemes may use fractional bits; but
- if a whole number, no `.' or `.0' is needed. See section (10).
- 7: m: constant (header/framing) overhead for From16 destination buffer,
- per channel, in bytes (an integer). See section (10).
- line 8: //more data if required...
-
- (1) getting the right piece of code.
-
- The Replay movie header contains textual information which is used to deduce
- the format of the data and thus which of the sound code files to use. The
- Replay MovingLines sound files should be used only if the sound type is 1 or 2.
- In format 2, the file header gives the exact name of the decompressor directory
- to use: add the string 'Play' and the number of tracks.
-
- For format 1, the SoundA/SoundS/SoundU/SoundE comes from the comments in the
- bits per sample field: if the text fragment ADPCM (in any case) is present,
- then SoundA; if LIN, then if UNSIGN then SoundU else SoundS; otherwise SoundE.
- 4/8/16 bits comes directly from the ARMovie header's bits per sample field.
-
- Mono/Stereo is easy: the number of channels in the ARMovie header is 1 for mono
- (no suffix on the file name) and 2 for stereo (suffix x2 on the file name).
-
- Stereo data for both formats 1 and 2 may be in normal left/right sense, or
- reversed: it is reversed if the sound channels field contains REVER (in any
- case).
-
- To play the desired track, the information for the correct fields must be
- extracted! The following BASIC fragment extracts a field from a string j$ for a
- desired track I%:
-
- DEF FNa(j$,I%)
- IFI%>1 j$=MID$(j$,INSTR(j$,"|"+STR$I%)+LENSTR$I%+2)
- IFINSTR(j$,"|") j$=LEFT$(j$,INSTR(j$,"|")-1)
- =j$
-
- Assuming an uppercasing function FNuc, then the following is correct code to
- find the name of the sound file for track 'track%' from an ARMovie opened as
- 'file%' reading from :
-
- FORI%=1TO9:a$=GET$#file%:NEXT: REM ignore video header
-
- snd$=FNa(GET$#file%,track%):snd%=VALsnd$:REM sound compression
- IFsnd%=1 OR snd%=2 THEN
- REM safe to read all the other stuff...
- sndrep=VALFNa(GET$#file%,track%): REM sound replay rate (fp value)
- lin$=FNa(GET$#file%,track%): REM sound channels
- reversed%=FALSE:IFINSTR(FNuc(lin$),"REVER") reversed%=TRUE
- channels%=VALlin$: REM channels variable
- lin$=FNa(GET$#file%,track%): REM sound precision/linear
- sndbits%=VALlin$: REM sound precision
- sndmul%=8: REM default value for storage
- IFsnd%=1 THEN
- IFsndbits%=16 OR INSTR(FNuc(lin$),"LIN") THEN
- IFINSTR(FNuc(lin$),"UNSIGN") lin$="U" ELSE lin$="S"
- ELSE
- IFsndbits%=4 OR INSTR(FNuc(lin$),"ADPCM") THEN
- lin$="A":sndmul%=16
- ELSE
- lin$="E"
- ENDIF
- ENDIF
- lin$+=STR$sndbits%
- lin$="Sound"+lin$: REM lin$ is now the name of the sound decompressor
- ELSE
- lin$=MID$(snd$,3)+" ": REM format asserts this is OK!
- lin$=LEFT$(lin$,INSTR(lin$," ")-1)
- f2info%=OPENIN(<ARMovie$SoundDir>."+lin$+".Info"):IFf2info% THEN
- snd$=GET$#f2info%:REM name
- snd$=GET$#f2info%:REM copyright
- snd$=GET$#f2info%:REM 0/1
- sndmul%=VALGET$#f2info%: REM bits per sample
- CLOSE#f2info%
- ENDIF
- lin$+=".Play"
- ENDIF
- IFchannels%>1 lin$+="x"+STR$channels%
-
- Now 'lin$' contains the suffix for the sound file name and a variable 'sndmul%'
- contains the number of bits per output sample required for decompression (for
- format 1, this is 8 or 16 depending on ADPCM - for which the decompression
- buffer needs to be larger than the sample buffer supplied). Variables
- 'reversed%', 'channels%', 'sndbits%' and 'sndrep' will be needed!
-
- (2) loading the code.
-
- The various sound code files are stored as "<ARMovie$SoundDir>."+lin$ and have
- different sizes: enough memory should be allocated to load the particular file:
- each file is position independant code which can be loaded into RMA memory or
- into application memory (from where it must not be multi-tasked out until
- finished with!). An array of entry points is at the beginning of the code.
-
- When a sound file (at least, one which drives the internal sound system) has
- been loaded it needs to establish the particular clock rate of the VIDC chip:
- this will usually be 24MHz, but may be 25.175MHz (VGA) or other frequencies if
- a 'VIDC enhancer' is in use. Therefore it is started playing silently,
- recording the sound interrupts. After a while the time and number of sound
- interrupts can be used to calculate the VIDC clock rate. A control state is
- passed in r0 and the address of a control block in r1. The size of the control
- block is 64 bytes (older versions of the sound code used just 8 bytes). The
- name mute% used below is historical: one of the control functions of the first
- word of the block is muting of sound during play.
-
- REM does it exist? how large is it?
- SYS"OS_File",17,"<ARMovie$SoundDir>."+lin$ TO r0%,,,,r4%
- IFr0%<>1 ERROR 1,"Sound playback code "+lin$+" not found"
- DIM sndcode% r4%-1,mute% 63: REM get memory for the sound code and control block
- SYS"OS_File",16,"<ARMovie$SoundDir>."+lin$,sndcode%: REM load it
-
- SOUND ON:REM configure the sound system to defaults
- SYS"Sound_Configure" TO oldchan%,,oldspeed%
-
- REM symbols for the entry points
- sndplay%=sndcode%: REM play the sound or start VIDC timing test.
- sndstop%=sndplay%+4: REM stop playing the sound
- snddata%=sndstop%+4: REM give data to be played
- sndcounter%=snddata%+4: REM count address
- sndifflags%=sndcounter%: REM sound interface flags (see below)
- sndbuffer%=sndcounter%+4: REM buffer address: 2 words
- sndpause=mute%+4: REM if 0 pause when sound data runs out, if
- non-zero don't pause
-
- When calling via sndplay%, the value in R0 is key to operation of the sound
- code.
-
- If R0 on the call is 0 then it's an old style timing check and the sound code
- will from then on assume that old code is calling it and will operate in a mode
- which is compatible with such code. Do not use this value in new code.
-
- If R0 on call is >= 0x10000 then it is a compatibility mode play call (and R0
- must have been 0 on the preceding sndplay% timing call); in this case it will
- start playing with R0 as the sample rate conversion fixed-point interpolation
- fraction (in 8.24 format), and R1 assumed to point at a 2 word control block,
- where the first word controls muting/stopping of the sound and the second is
- used to determine the treatment of data underrun - see section (4).
-
- If R0 = 1 or 2 then it is respectively a new-style timing check or play call.
- R1 points at an information block. The first two words of this block have the
- same meaning as with old-style calls. For the play call, R1 is assumed to point
- at a 16-word information block (format given below) which tells the sound code
- what it needs to know: the contents of this block are maintained partly by the
- caller, partly by the sound code. Certain parts must be be initialised by the
- caller (see below) before the first play call.
-
- Values of R0 between 3 and 0xFFFF inclusive are currently reserved and must not
- be used.
-
- The values to initialise the control block (pointed at by mute% in this example
- code) are as follows:
-
- REM mute%!0 is global flags word (caller-maintained)
- mute%!0 = 0: REM clear all flags
-
- REM mute%!4 is soundpause flag: see section (5)
-
- REM mute%!8 and !12 are the source sample frequency, as a 32.24
- REM fixed-point unsigned number, measured in Hz.
-
- This initialisation must be done before the sndplay% call to start playing the
- data. It can also be done anytime earlier (obviously!) as convenient.
-
- The value sndrep (a real +ve quantity) taken from the Replay file header, is
- the sample frequency in Hz if >= 256.0, or sample period in usec if < 256.0 We
- need the frequency form here.
-
- sfr = sndrep: IF sfr <= 255 THEN sfr = 1E6/sfr
- mute%!8 = INT(sfr): REM whole part of frequency
- mute%!12 = (1<<24) * (sfr - INT(sfr)): REM fractional part, to 24 bits
-
- REM mute%?16 is requested quality flag, treated as an unsigned byte.
-
- Defined quality range is 1..4 (1=lowest quality .. 4=highest). It is up to the
- caller to put in a 'suitable' value. It is up to the sound code as to what to
- do with this.
-
- As an aid in making the decision, bit 1 of !sndifflags% (only well-defined once
- the initial (timing) sndplay% call has returned) provides a hint from the sound
- code. It is used to indicate whether quality is `free', or whether it has a
- significant cost in terms of cpu or bus bandwidth. If set, there is a
- significant cpu cost factor in using higher quality levels, over and above the
- normal cost of simply moving the original data around once (or perhaps twice)
- at its natural sample rate.
-
- Some sound hardware may be flexible and/or powerful enough to keep the ARM
- processing cost to little more than moving the base-rate data around, in which
- case !sndifflags% AND 2 would be 0, and the caller would in general then set
- the quality level to 4 (or perhaps allowing user choice if appropriate).
-
- In other cases the sound code may use more cpu cycles and/or memory-bandwidth
- with increasing quality factor, so the caller may therefore choose an
- appropriate quality factor after consideration of the available performance of
- the processor/memory system in use (clock speed(s), bus rates, cache, etc), and
- also with regard to the demands of any video data processing which may be going
- on.
-
- For internal (VIDC) sound, this bit is set, since oversampling is used (with
- the factor determined by the quality level), and the processor must therefore
- deal with more and more generated data at higher quality levels, for the same
- source sample rate. The quality value controls the output sample rate used: 1:
- 13.889kHz (72usec), 2: 20.833kHz (48usec) 3: 31.250kHz (32usec) 4: 41.667kHz
- (24usec); value 0 is treated as 1, values >4 are treated as 4. However, if the
- basic source sample rate is over 14.0 kHz and the quality level is 1, then
- 48usec is used, to avoid too much noise from the implied undersampling.
-
- mute%?16 = 4: REM (using highest quality in this example)
-
- There is one remaining item defined in the control block:
-
- REM mute%?17 is stereo channel ordering: 0 (norm) is {L,R}, 1 is {R,L}.
- REM In mono case, value is ignored by sound code.
- mute%?17 = reversed% AND 1
-
- All the remaining bytes in the control block must be zero, for now.
-
- REM Clear remaining control words, for future expandability.
- FOR I% = 18 TO 63: mute%?I% = 0: NEXT
-
- The first call on the sound code is a timing call, via sndplay%:
-
- REM start sound timing check
- !mute%=0: A%=1: B%=mute%:sndtime%=TIME:CALLsndplay%:sndtime%=TIME
- [ old code:
- !mute%=0: A%=0: B%=mute%:sndtime%=TIME:CALLsndplay%:sndtime%=TIME
- ]
-
- REM if something goes wrong from now on, then this is how to put it back
- ON ERROR CALLsndstop%:REPORT:PRINT" at line ";ERL:END
-
- The purpose of the timing call is to allow for variable rate master clocks used
- to drive the sound output sample rate. This problem is only relevant on
- VIDC1-based systems, some of which have VIDC-enhancer (multiple video clock)
- circuitry, either as standard or as a hardware enhancement: the video and sound
- clocks are linked within VIDC1. However, the sound code may know (or be able to
- work out) that the master clock driving the sound sample rate does not vary
- with video mode, e.g. it might actually be driving separate sound output
- hardware (not linked to VIDC), or be driving VIDC20 which has a separate sound
- clock input.
-
- Normally (for current and old machines), the master clock for the sound may be
- variable according to video mode, so the timing test needs to go on for a
- while: the timing will be accurate enough after 1 second. In this case,
- initialise other parts of the system or calculate or whatever, in the
- intervening time.
-
- If the master sound clock rate does not vary with video mode, then the sound
- code will set bit 0 of !sndifflags% during the initial sndplay% timing call.
- Hence the calling code can do the sndstop% call (still necessary) whenever
- it wishes, and does not need to wait for the normal minimum of a whole
- second. See code in section (4).
-
- Once the timing test has been performed, the sound code will have worked out
- the current sound master clock rate, and will keep a record of this (but see
- next paragraph). It will discard the information if another timing test call
- is made, since it does not itself know when the video mode changes. It is up
- to the caller to know that the video mode has been (or might have been)
- changed, and hence to re-do the timing test. Conversely, the timing test
- does not need to be redone (provided that the same sound code module remains
- resident, untouched) if the caller is sure that the video clock frequency
- has not been changed, or if the sound master clock rate is independent of
- video mode, as may be indicated on the first timing call. However, the
- timing test MUST be done at least once after loading new sound code.
-
- For internal VIDC sound (which is expected to be the only sound output type
- for which the timing may vary with video mode), the timing check cannot be
- done if sound is globally disabled (via *audio off or the Sound_Enable SWI),
- since no sound DMA interrupts then occur. Hence, after the initial timing
- test sndplay% call has returned, if bit 0 of !sndifflags% is not set (and
- therefore the sound timing is potentially variable with video mode), bit 2
- should also be checked. If this is set, it identifies the fact that the
- internal sound system was found to be globally disabled. This has two
- consequences: (a) no sound will be output during the subsequent sound
- playing time (the data passed to snddata% calls will just be thrown away);
- (b) the sound code cannot determine the master clock frequency, and so
- cannot record it - therefore the timing test must be performed again, each
- time sound is to be played, until the global sound system is finally
- re-enabled.
-
- (3) setting up the sound buffers.
-
- The sound play code needs 2 buffers of the duration of the sample chunk size -
- i.e. 'maximum possible number of samples in a sound chunk'*'sndmul%'/8 plus
- some space for semaphore flags: another 16 bytes - all word aligned. Compute
- the maximum number of samples in a sound chunk by reading the ARMovie 'sndrep'
- sound repetition rate variable, the 'frames per chunk' and the 'frames per
- second' variables and add 1% of this number of samples to allow for variations
- in capture programs. Then allocate the buffers, and in each set the word at
- byte offset 4 to 1 to signal them being empty and set !sndbuffer% and
- sndbuffer%!4 to the addresses:
-
- IFsndrep<256 THEN
- Z%=(fpf/fps*(1E6/sndrep)*channels%*1.01*sndmul%+7)DIV8+12+4+3ANDNOT3
- ELSE
- Z%=(fpf/fps*sndrep*channels%*1.01*sndmul%+7)DIV8+12+4+3ANDNOT3
- ENDIF
- DIM sndbA% Z%-1,sndbB% Z%-1
- sndbA%!4=1:sndbB%!4=1
- !sndbuffer%=sndbA%:sndbuffer%!4=sndbB%
-
- Obviously this can be done at any time, but it is something to do while the
- sound playback timing test is being performed.
-
- (4) calculating the system sound clock frequency.
-
- This is now done (if necessary) by the sound code itself, either on the
- sndstop% call after the timing test, or in the subsequent sndplay% call.
- Applications using the sound code in the new standard mode only need to stop
- the timing check. Floating point is used, so the FPEmulator (or whatever is
- providing ARM FP instructions) should be present.
-
- REM Wait for the rest of the second since starting the timing
- REM check, if required, to ensure timing accuracy.
- IF (!sndifflags% AND 1) = 0 THEN
- REPEAT UNTIL TIME-sndtime%>99
- ENDIF
-
- REM stop the timing check
- CALLsndstop%:sndtime%=TIME-sndtime%
-
- [For old code, after the requisite second, the sound play code was stopped and
- the VIDC clock frequency calculated. The following code computes the frequency
- and also 'snaps' values near to 25.175MHz or an integral number of MHz to those
- numbers:
-
- IFsndcounter%!12 sndfreq=sndcounter%!16/sndcounter%!12*sndcounter%!20*24 ELSE sndfreq=24
- C%=sndfreq+.5
- IFsndfreq>.998*25.175 IFsndfreq<1.002*25.175 sndfreq=25.175
- IFC%*1.002>sndfreq IFC%*.998<sndfreq sndfreq=C%
-
- 'sndfreq' is the VIDC clock rate. The sound code uses a fixed point number to
- express the ratio faster or slower to play the sample:
-
- sndratio%=24/sndfreq*(1<<24)
-
- gives 'sndratio%' which will be used to cause the sound code to play the sample
- at the correct rate.]
-
- (5) configuring the system for playing the sound.
-
- Most of this is done by the sound code itself, during the main sndplay% call
- (see section (7) below), using information in the block pointed to by R1 on
- that call.
-
- [In old code, the RISC OS sound system had to be configured by the caller to
- play the sound properly: this requires selecting the right number of channels,
- and the right basic sound sample rate. Oversampling (selecting a higher play
- rate than the sample was recorded at) improves the sound at the cost of greater
- processing (more cpu cycles used): the variable 'slow%' is TRUE for a slow
- machine. The 'sndrep' value is taken to mean a period in uS if less than 256 or
- a frequency in Hz if greater.
-
- IFsndrep>255 THEN
- sndrep=sndrep*speed
- sndplayrate%=24:IFslow% sndplayrate%=72
- SYS"Sound_Configure",channels%,0,sndplayrate%
- sndratio%=sndratio%*(sndplayrate%/(1E6/sndrep))
- ELSE
- sndrep=sndrep/speed
- IFslow% OR sndrep<48 THEN
- SYS"Sound_Configure",channels%,0,sndrep
- ELSE
- SYS"Sound_Configure",channels%,0,sndrep DIV3
- sndratio%=sndratio%DIV3
- ENDIF
- ENDIF
- IFchannels%=1 THEN
- SYS"Sound_Stereo",1,0:REM mono in the middle
- ELSE
- IFreversed% THEN
- SYS"Sound_Stereo",2,-127:SYS"Sound_Stereo",1,127
- ELSE
- SYS"Sound_Stereo",1,-127:SYS"Sound_Stereo",2,127
- ENDIF
- ENDIF
- ]
-
- !sndpause=0
-
- The actual value set in here is treated as a zero/non-zero flag. Zero means
- that the sound code should automatically pause whenever sound data runs out;
- that is, it will stop incrementing its view of logical time (amount of sound
- data played/skipped), and will wait for, and play, late data, rather than
- ignoring it and skipping past it when it does arrive. When non zero, it does
- the converse, and data arriving after its "play-by" time is skipped over, in
- order to catch up with real time.
-
- Set this flag to the desired value before starting to play the sound, and do
- not change this flag until sound playing has been stopped, or you will horribly
- confuse the sound code!
-
- (6) giving data to the sound code.
-
- The sound code needs to be continually fed data via its 'snddata%' routine: r0
- specifies the address of the data, r1 the number of bytes: after the routine
- returns, the source is not required anymore. The snddata% routine will wait for
- a buffer to become available - callers can check the value at offset 4 in the
- sound buffer: if this is non-zero in either buffer, then a call to snddata%
- will return as quickly as possible.
-
- IFsndbA%!4<>0 OR sndbB%!4<>0 THEN
- A%=dataptr%:B%=length%:CALLsnddata%
- ENDIF
-
- The sound code itself doesn't modify interrupt status (other than disabling
- them for very short periods during internal operations), so interrupts should
- be enabled around the call to ensure that IRQ latency doesn't get hit by e.g.
- ADPCM decompression time.
-
- (7) starting it playing.
-
- After giving some data to 'snddata%', the sound code can be started:
-
- [ Old style:
- A%=sndratio%:B%=mute%:CALLsndplay%
- ]
-
- A% = 2: B% = mute%: CALL sndplay%
-
- (8) in flight entertainment.
-
- While the sound code is playing, the value at address 'mute%' can be changed so
- as to control it. Bit zero is pause (if set), bit 1 is mute (if set).
-
- Whilst sound is playing (even if muted), the sound code maintains a pair of
- words (sndcounter%!24 and sndcounter%!28), representing the time as far as the
- sound code is concerned, i.e. how far through the sound track it had got at the
- most recent sound IRQ. This is measured in units of input sample times, that
- is, 1/(sample playback frequency as specified in the file); the number of
- channels in use is not significant in this calculation. sndcounter%!24 records
- the number of whole sample times so far, and sndcounter%!28 is the fractional
- part (which must be maintained, for accuracy, but can be ignored by code
- checking the sound time).
-
- temp_time%=start_time%+((sndcounter%!24/my_sample_rate%)*100)
-
- (9) stopping.
-
- A call to 'sndstop%' stops the sound code. If this is using the RISC OS sound
- system, it will itself put the RISC OS sound parameters back as they were
- [unless the corresponding sndplay% call was an old-style one].
-
- CALLsndstop%
- [ old code had to do:
- SYS"Sound_Configure",oldchan%,0,oldspeed%
- ]
-
- (10) format 2: To16 and From16.
-
- These convert a chunk's worth of samples between the compressed format and raw
- 16 bit signed linear (sample-by-sample interleaved for multi-channel formats).
- The code has one entry point at the beginning and takes the following
- parameters in registers:
-
- r0: source data
- r1: destination data
- r2: number of sample-time units in the chunk
- r3: number of channels
-
- On exit it returns updated values of source and destination registers. The
- values of r0 and r1 should be word aligned. The value in r2 is measured in
- sample-time units, and is independent of the number of channels involved.
-
- The correct number of channels for the data in hand should always be passed in
- r3. If the converter cannot handle conversion of the specified number of
- channels, for whatever reason, it will return with r0 zero, and no data will
- have been written into the destination buffer.
-
- For To16, the required size of the destination data buffer in bytes is easy to
- calculate:
-
- destsize = number of sample times * number of channels * 2
-
- For From16, details from the Info file must be used to compute the maximum
- required size of the destination buffer. Take the number of sample-times the
- source data represents (s%), multiply by c, convert this from (possibly
- fractional) total bits to whole bytes (add 1 byte for certainty), round up to 4
- bytes, add the per-channel byte overhead (m%), and multiply by the number of
- channels. Hence:
-
- destbuffersize% = ((INT(s% * c / 8 + 1) + 3) DIV 4 * 4 + m) * chans%
-
-